﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Streambolics.App
{
    public delegate void ModuleRecoveryProcedure (ComputingModule aModule);

    /// <summary>
    ///     A strategy to recover a failed module
    /// </summary>

    public class ModuleRecoveryStrategy
    {
        private Type _ModuleType;
        private Type _ErrorType;
        private ModuleRecoveryProcedure _Procedure;
        private int _MaxAttempts;
        private TimeSpan _FirstDelay;
        private TimeSpan _NextDelay;
        private TimeSpan _RecoveryDelay;

   
        /// <summary>
        ///     Create a Module Recovery Strategy
        /// </summary>
        /// <param name="aModuleType">
        ///     The type of module on which the strategy will be attempted.
        /// </param>
        /// <param name="aErrorType">
        ///     The type of error the strategy will attempt to correct.
        /// </param>
        /// <param name="aProcedure">
        ///     The procedure to be applied.
        /// </param>

        public ModuleRecoveryStrategy (Type aModuleType, Type aErrorType, ModuleRecoveryProcedure aProcedure)
        {
            _ModuleType = aModuleType;
            _ErrorType = aErrorType;
            _Procedure = aProcedure;
            _MaxAttempts = 5;
            _FirstDelay = 5.Seconds ();
            _NextDelay = 5.Seconds ();
            _RecoveryDelay = 5.Minutes ();
        }

        /// <summary>
        ///     The maximum number of times this strategy should be attempted
        ///     on a module.
        /// </summary>

        public int MaxAttempts
        {
            get
            {
                return _MaxAttempts;
            }
            set
            {
                _MaxAttempts = value;
            }
        }

        public ModuleRecoveryStrategy WithMaxAttempts (int aMaxAttempts)
        {
            MaxAttempts = aMaxAttempts;
            return this;
        }

        /// <summary>
        ///     The delay between the detection of the failure and the first
        ///     attempt.
        /// </summary>

        public TimeSpan FirstDelay
        {
            get
            {
                return _FirstDelay;
            }
            set
            {
                _FirstDelay = value;
            }
        }

        public TimeSpan NextDelay
        {
            get
            {
                return _NextDelay;
            }
            set
            {
                _NextDelay = value;
            }
        }

        /// <summary>
        ///     The recovery delay
        /// </summary>
        /// <remarks><para>
        ///     This is the time after which the error will be considered recovered
        ///     if the module no longer exhibits the error.
        /// </para></remarks>

        public TimeSpan RecoveryDelay
        {
            get
            {
                return _RecoveryDelay;
            }
            set
            {
                _RecoveryDelay = value;
            }
        }

        /// <summary>
        ///     Whether the strategy applies to the given module
        /// </summary>
        /// <param name="aModule">
        ///     The module
        /// </param>
        /// <returns>
        ///     True if the strategy can be applied to the module,
        ///     false otherwise. The number of times the strategy
        ///     has already been attempted is not taken into account.
        /// </returns>

        public bool AppliesTo (ComputingModule aModule)
        {
            return aModule.GetType ().IsClassOrSubclassOf (_ModuleType)
                && aModule.Failure.GetType ().IsClassOrSubclassOf (_ErrorType);
        }

        /// <summary>
        ///     Performs the first recovery attempt on the module.
        /// </summary>
        /// <param name="aModule">
        ///     The module to recover.
        /// </param>
        /// <param name="aHistory">
        ///     The recovery history of the module.
        /// </param>
        /// <returns>
        ///     True if the recovery has been attempted (even if it failed)
        ///     false otherwise.
        /// </returns>

        internal bool FirstAttempt (ComputingModule aModule, ModuleRecoveryHistory aHistory)
        {
            if (_FirstDelay.ElapsedSince (aHistory.FailureDetection))
            {
                PerformAttempt (aModule);
                return true;
            }
            else
            {
                return false;
            }
        }

        internal bool NextAttempt (ComputingModule aModule, ModuleRecoveryAttempt aAttempt)
        {
            if (aAttempt.TotalAttempts < _MaxAttempts
                && _NextDelay.ElapsedSince (aAttempt.LastAttempted))
            {
                PerformAttempt (aModule);
                return true;
            }
            else
            {
                return false;
            }
        }

        private void PerformAttempt (ComputingModule aModule)
        {
            try
            {
                _Procedure (aModule);
            }
            catch (Exception ex)
            {
                aModule.DeclareFailed (ex);
            }
        }
    }
}
